#include <syslog.h>
#include <cassert>
#include <cerrno>
#include <cstring>
#include <stdexcept>
#include <limits>
#include <unistd.h>
#include <limits.h>
#include <fuse.h>
#include "fuseVMData.h"
#include "vsphere_handle.h"
#include "huawei_handle.h"
#include "util.h"

extern struct vmfuse_param param;
#define FZ_ATTR_HARDLINK (0x800)
#define PROGRAM "vmfuse"

FuseVMData::FuseVMData(const char *cwd) : m_cwd(cwd)
{
}

FuseVMData::~FuseVMData()
{
    for (filemap_t::iterator i = files.begin(); i != files.end(); ++i)
    {
        delete i->second;
    }
}

void FuseVMData::build_tree()
{
    m_root = FileNode::createRootNode();
    if (m_root == NULL)
    {
        throw std::bad_alloc();
    }
    m_root->parent = NULL;
    files[m_root->full_name.c_str()] = m_root;
    if(param.type != TYPE_NONE)
    {
        int file_index = 1;
        if(strlen(param.fileName))
        {
            attachNode(file_index, param.type, param.fileName);
            file_index++;
        }
        if(strlen(param.backupFilelist) && strlen(param.backupDisklist))
        {
            char filePath[512] = {0};
            char fileName[1024] = {0};
            char diskName[512] = {0};
            char *token;
            const char *last_comma = strrchr(param.backupFilelist, ',');
            if(last_comma == NULL)
            {
                strcpy(filePath, param.backupFilelist);
            }
            else
            {
                strcpy(filePath, last_comma + 1);
            }
            strcpy(diskName, param.backupDisklist);
            token = strtok(diskName, ",");
            while(token!= NULL)
            {
                sprintf(fileName, "%s/%s", filePath, token);
                attachNode(file_index, param.type, fileName);
                file_index++;
                token = strtok(NULL, ",");
            }
        }
        // Connect nodes to tree. Missing intermediate nodes created on demand.
        for (filemap_t::const_iterator i = files.begin(); i != files.end(); ++i)
        {
            printf("%s: add files [%s]\n", __FUNCTION__, i->first);
            FileNode *node = i->second;
            if (node != m_root)
            {
                connectNodeToTree(node);
            }
        }
    }
    printf("%s: build_tree done, files count [%d]\n", __FUNCTION__, (int)numFiles());
}

FileNode *FuseVMData::find(const char *fname) const
{
    filemap_t::const_iterator i = files.find(fname);
    if (i == files.end())
    {
        return NULL;
    }
    else
    {
        return i->second;
    }
}

FileNode *FuseVMData::findParent(const FileNode *node) const
{
    std::string name = node->getParentName();
    return find(name.c_str());
}

void FuseVMData::connectNodeToTree(FileNode *node)
{
    FileNode *parent = findParent(node);
    if (parent == NULL)
    {
        parent = FileNode::createIntermediateDir(node->getParentName().c_str());
        if (parent == NULL)
        {
            throw std::bad_alloc();
        }
        files[parent->full_name.c_str()] = parent;
        connectNodeToTree(parent);
    }
    else if (!parent->is_dir())
    {
        throw std::runtime_error("bad files structure");
    }
    // connecting to parent
    node->parent = parent;
    parent->appendChild(node);
}

char *FuseVMData::get_name(std::string full_name)
{
    assert(!full_name.empty());

    const char *lsl = full_name.c_str();
    while (*lsl++)
    {
    }
    lsl--;
    while (lsl > full_name.c_str() && *lsl != '/')
    {
        lsl--;
    }
    // If the last symbol in file name is '/' then it is a directory
    if (*lsl == '/' && *(lsl + 1) == '\0')
    {
        // It will produce two \0s at the end of file name. I think that
        // it is not a problem
        full_name[full_name.size() - 1] = 0;
        while (lsl > full_name.c_str() && *lsl != '/')
        {
            lsl--;
        }
    }
    // Setting short name of node
    if (*lsl == '/')
    {
        lsl++;
    }
    return const_cast<char *>(lsl);
}

void FuseVMData::attachNode(int64_t id, int type, const char *name)
{
    if (type == TYPE_FILE)
    {
        char absPath[PATH_MAX];
        char shortName[PATH_MAX];
        if (realpath(name, absPath) == NULL)
        {
            syslog(LOG_ERR, "wrong filename: %s", name);
            throw std::runtime_error("wrong filename");
        }
        std::string s_absPath = absPath;
        strcpy(shortName, get_name(s_absPath));
        if (files.find(shortName) != files.end())
        {
            syslog(LOG_ERR, "duplicated file name: %s", shortName);
            throw std::runtime_error("duplicate file names");
        }
        struct stat fileStat;
        if (stat(absPath, &fileStat) == -1)
        {
            syslog(LOG_ERR, "stat failed: %s", absPath);
            throw std::runtime_error("stat failed");
        }
        FileNode *node = FileNode::createNodeForExisting(shortName, absPath, id, type, fileStat);
        if (node == NULL)
        {
            throw std::bad_alloc();
        }
        files[node->full_name.c_str()] = node;
    }
    else if (type == TYPE_VSPHERE)
    {
        char absPath[PATH_MAX];
        char shortName[PATH_MAX];
        char metadataPath[PATH_MAX+9];
        if (realpath(name, absPath) == NULL)
        {
            syslog(LOG_ERR, "wrong filename: %s", name);
            throw std::runtime_error("wrong filename");
        }
        std::string s_absPath = absPath;
        strcpy(shortName, get_name(s_absPath));
        if (files.find(shortName) != files.end())
        {
            syslog(LOG_ERR, "duplicated file name: %s", shortName);
            throw std::runtime_error("duplicate file names");
        }
        struct stat fileStat;
        if (stat(absPath, &fileStat) == -1)
        {
            syslog(LOG_ERR, "stat failed: %s", absPath);
            throw std::runtime_error("stat failed");
        }
        //create data file
        char cbt_json_file[PATH_MAX+10];
        json_t *json_config = NULL;
        sprintf(cbt_json_file, "%s_cbt.json", absPath);
        process_json_work_file(cbt_json_file, &json_config);
        off_t len = get_length(json_config);
        //printf("vSphere disk[%s] size: %ld\n", shortName, len);
        if (json_config)
        {
            json_decref(json_config);
        }
        fileStat.st_size = len;
        fileStat.st_mode = S_IFREG | 0644;
        fileStat.st_mtime = time(NULL);
        char datafile_shortName[PATH_MAX];
        strcpy(datafile_shortName, shortName);
        datafile_shortName[strlen(datafile_shortName) - 5] = '\0';
        strcat(datafile_shortName, "-flat.vmdk");
        FileNode *flat_node = FileNode::createNodeForExisting(datafile_shortName, absPath, id, type, fileStat);
        if (flat_node == NULL)
        {
            throw std::bad_alloc();
        }
        if(!param.memoryFlag && strlen(param.storeDir))
        {
            std::string file_path = std::string(param.storeDir) + "/" + datafile_shortName;
            if (create_sparse_file(file_path.c_str(), len)) {
                throw std::runtime_error("create_sparse_file failed");
            }
            int store_file = open(file_path.c_str(), O_RDWR);
            if (store_file < 0)
            {
                syslog(LOG_ERR, "vsphere_write:failed to open store_name %s,with %s", file_path.c_str(), strerror(errno));
                fprintf(stderr, "%s:vsphere_write:failed to open store_name %s,with %s\n", PROGRAM, file_path.c_str(), strerror(errno));
                return;
            }
            flat_node->_store_file = store_file;
            flat_node->_store_name = file_path;
        }
        files[flat_node->full_name.c_str()] = flat_node;
        //create metadata file
        char buf[8192];
        char r_buf[8192];
        FileNode *node = FileNode::createFile(shortName, fuse_get_context()->uid, fuse_get_context()->gid, S_IFREG | 0644, 0);
        if (node == NULL)
        {
            throw std::bad_alloc();
        }
        files[node->full_name.c_str()] = node;
        memset(buf, 0, sizeof(buf));
        strcat(buf, "# Disk DescriptorFile\n");
        strcat(buf, "version=3\n");
        strcat(buf, "encoding=\"UTF-8\"\n");
        strcat(buf, "CID=fffffffe\n");
        strcat(buf, "parentCID=ffffffff\n");
        strcat(buf, "createType=\"vmfs\"\n");
        strcat(buf, "\n");
        strcat(buf, "# Extent description\n");
        memset(r_buf, 0, sizeof(r_buf));
        sprintf(r_buf, "RW %ld VMFS \"%s", len / DEFAULT_SECTOR_SIZE, shortName);
        r_buf[strlen(r_buf) - 5] = '\0';
        strcat(r_buf, "-flat.vmdk\"\n");
        strcat(buf, r_buf);
        strcat(buf, "\n");
        sprintf(metadataPath, "%s.metadata", absPath);
        int fd = open(metadataPath, O_RDONLY);
        if (fd < 0)
        {
            syslog(LOG_ERR, "failed to open %s", metadataPath);
            return;
        }
        memset(r_buf, 0, sizeof(r_buf));
        read_vsphere_metadata_data(fd, r_buf);
        close(fd);
        strcat(buf, "# The Disk Data Base\n");
        strcat(buf, "#DDB\n");
        strcat(buf, "\n");
        strcat(buf, r_buf);
        node->write(buf, strlen(buf), 0);
    }
    else if (type == TYPE_FSPHERE)
    {
        char absPath[PATH_MAX];
        char shortName[PATH_MAX];
        if (realpath(name, absPath) == NULL)
        {
            syslog(LOG_ERR, "wrong filename: %s", name);
            throw std::runtime_error("wrong filename");
        }
        std::string s_absPath = absPath;
        strcpy(shortName, get_name(s_absPath));
        if (files.find(shortName) != files.end())
        {
            syslog(LOG_ERR, "duplicated file name: %s", shortName);
            throw std::runtime_error("duplicate file names");
        }
        struct stat fileStat;
        if (stat(absPath, &fileStat) == -1)
        {
            syslog(LOG_ERR, "stat failed: %s", absPath);
            throw std::runtime_error("stat failed");
        }
        //create data file
        char cbt_json_file[PATH_MAX+10];
        json_t *json_config = NULL;
        sprintf(cbt_json_file, "%s_cbt.json", absPath);
        huawei_process_json_work_file(cbt_json_file, &json_config);
        off_t len = huawei_get_length(json_config);
        //printf("vSphere disk[%s] size: %ld\n", shortName, len);
        if (json_config)
        {
            json_decref(json_config);
        }
        fileStat.st_size = len;
        fileStat.st_mode = S_IFREG | 0644;
        fileStat.st_mtime = time(NULL);
        FileNode *flat_node = FileNode::createNodeForExisting(shortName, absPath, id, type, fileStat);
        if (flat_node == NULL)
        {
            throw std::bad_alloc();
        }
        if(!param.memoryFlag && strlen(param.storeDir))
        {
            std::string file_path = std::string(param.storeDir) + "/" + shortName;
            if (create_sparse_file(file_path.c_str(), len)) {
                throw std::runtime_error("create_sparse_file failed");
            }
            flat_node->_store_name = file_path;
        }
        files[flat_node->full_name.c_str()] = flat_node;
    }
}

void FuseVMData::insertNode(FileNode *node)
{
    FileNode *parent = findParent(node);
    assert(parent != NULL);
    parent->appendChild(node);
    node->parent = parent;
    parent->setCTime(node->ctime());
    assert(files.find(node->full_name.c_str()) == files.end());
    files[node->full_name.c_str()] = node;
}

int FuseVMData::removeNode(FileNode *node)
{
    assert(node != NULL);
    assert(node->parent != NULL);
    node->parent->detachChild(node);
    node->parent->setCTime(currentTime());
    files.erase(node->full_name.c_str());
    delete node;
    return 0;
}

void FuseVMData::renameNode(FileNode *node, const char *newName, bool reparent)
{
    assert(node != NULL);
    assert(newName != NULL);
    FileNode *parent1 = node->parent, *parent2;
    assert(parent1 != NULL);
    if (reparent)
    {
        parent1->detachChild(node);
    }

    files.erase(node->full_name.c_str());
    node->rename(newName);
    files[node->full_name.c_str()] = node;

    if (reparent)
    {
        parent2 = findParent(node);
        assert(parent2 != NULL);
        parent2->appendChild(node);
        node->parent = parent2;
    }

    if (reparent && parent1 != parent2)
    {
        struct timespec now = currentTime();
        parent1->setCTime(now);
        parent2->setCTime(now);
    }
}